Rust Axum 框架中集成 OpenAPI (Swagger)

Rust Axum 框架中集成 OpenAPI (Swagger)

1. 概述与目标

本指南详细介绍如何在基于 Rust 的 Axum 框架中,实现 OpenAPI (Swagger) 规范的集成。我们将采用 utoipa 库实现代码优先 (Code-first) 的 API 文档生成,并利用 utoipa-swagger-ui 部署交互式文档界面。

目标是实现清晰、自动化的 API 文档和测试界面,同时解决 Axum 0.7+ 版本与 utoipa-swagger-ui 9.x.x 版本的兼容性问题。

2. 技术选型与依赖配置

2.1 核心依赖

库名 版本 (示例) 用途 关键特性 (Feature)
utoipa 5.4.0 核心库,用于通过宏注解生成 OpenAPI JSON 规范。 axum_extras, serde_json
utoipa-swagger-ui 9.0.2 提供静态文件和路由,用于部署 Swagger UI 界面。 axum
serde 1.0 数据结构序列化/反序列化。 derive

2.2 Cargo.toml 配置示例

[dependencies]
# ... 其他依赖 ...
serde = { version = "1.0", features = ["derive"] }
# OpenAPI
utoipa = { version = "5.4.0", features = ["axum_extras", "serde_json"] }
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }

3. 代码结构建议

为了模块化和清晰地管理 API,建议采用以下结构:

路径 职责
src/api/ API 模块根目录
src/api/mod.rs 模块入口,包含 OpenAPI 定义 (ApiDoc)。
src/api/models.rs 包含所有请求和响应体的 数据结构体
src/api/[feature].rs 存放特定功能的 API 处理器函数 (handlers)。

4. utoipa 核心集成步骤

4.1 数据模型注解 (src/api/models.rs)

所有用于请求体或响应体的结构体都必须添加 #[derive(ToSchema)],这是 utoipa 识别数据类型的关键。

// 示例:健康检查响应和业务模型
use utoipa::ToSchema;
use serde::{Serialize, Deserialize};

#[derive(ToSchema, Serialize, Deserialize)]
#[aliases(Health = HealthStatus)] // 可选:提供更友好的 OpenAPI Schema 名称
pub struct HealthStatus {
    pub status: String,
    pub service: String,
    pub version: String,
}

#[derive(ToSchema, Serialize, Deserialize)]
pub struct FeatureType {
    pub id: u32,
    pub name: String,
    pub description: Option<String>,
}

#[derive(ToSchema, Serialize, Deserialize)]
pub struct SubmissionRequest {
    pub value: u64,
    pub feature_type_id: u32,
}

4.2 API 文档定义 (src/api/mod.rs)

使用 #[derive(OpenApi)] 宏创建文档入口点 (ApiDoc),配置元数据、数据模型和所有 API 路径。

use utoipa::OpenApi;
use crate::api::models::{HealthStatus, FeatureType, SubmissionRequest};
// 导入所有 handler 函数

#[derive(OpenApi)]
#[openapi(
    // 1. 定义 API 的元数据
    info(title = "通用 Axum API 文档", version = "v0.1.0"),
    // 2. 引用所有用于文档的数据结构体
    components(schemas(
        HealthStatus,
        FeatureType,
        SubmissionRequest,
        // ... 其他模型 ...
    )),
    // 3. 列出所有带有 #[utoipa::path(...)] 注解的 handler 函数
    paths(
        // 引用不同模块中的 handler 函数
        crate::api::health::health_check,
        crate::api::feature::get_feature_types,
        // ... 其他 handler ...
    ),
    // 4. 定义标签用于分组
    tags(
        (name = "Health", description = "健康检查"),
        (name = "Feature", description = "通用功能"),
    )
)]
pub struct ApiDoc;

4.3 处理器函数注解 (src/api/[feature].rs)

所有 API 处理器函数(Handlers)必须添加 #[utoipa::path(...)] 宏来定义路径、方法、参数和响应。文档注释 (///) 将作为方法的描述。

// 示例:GET /health
/// 获取服务健康状态
#[utoipa::path(
    get,
    path = "/health",
    responses(
        (status = 200, description = "Service is up and running", body = HealthStatus),
        (status = 500, description = "Internal Server Error"),
    ),
    tag = "Health"
)]
pub async fn health_check() -> Result<Json<HealthStatus>, StatusCode> {
    // ... 逻辑实现 ...
    todo!()
}

// 示例:GET /features
/// 获取所有可用功能类型
#[utoipa::path(
    get,
    path = "/features",
    responses(
        (status = 200, description = "返回功能列表", body = [FeatureType]),
    ),
    tag = "Feature"
)]
pub async fn get_feature_types() -> Result<Json<Vec<FeatureType>>, StatusCode> {
    // ... 逻辑实现 ...
    todo!()
}

5. Axum 路由集成与兼容性修复

这是 Axum 0.7+ 和 utoipa-swagger-ui 9.0.2 集成的关键部分。

5.1 兼容性问题与解决方案

错误类型 原因 解决方案 (针对 v9.0.2)
E0308 (mismatched types) Axum .merge() 无法推断 SwaggerUi 的类型。 SwaggerUi::new(...).url(...) 链的末尾显式调用 .into()
panic (Nesting at the root) Axum 0.7+ 禁止使用 .nest("/", ...) 移除根嵌套,直接将业务路由作为基础路由起点。
no method named into_make_service utoipa-swagger-ui 9.x.x 版本中,此类转换方法被移除。 改用 .into()

5.2 路由集成代码示例 (src/main.rs)

use utoipa_swagger_ui::SwaggerUi;
use axum::Router;

async fn main() {
    // 假设 create_router 是您的业务 API 路由函数
    let app_router = create_router(state); // state 假设是应用状态

    // 1. 创建 Swagger UI 路由
    let swagger_router = SwaggerUi::new("/swagger-ui") // Swagger UI 访问路径
        .url("/api-docs/openapi.json", ApiDoc::openapi()) // OpenAPI 规范文件路径
        // 关键兼容性修复:将 SwaggerUi 转换为 Axum Router 可接受的类型。
        .into();

    // 2. 合并路由并应用中间件
    let app = app_router
        .merge(swagger_router) // 使用 merge 方法合并 Swagger 路由
        .layer(cors); // 应用中间件(例如 CORS)

    // ... 服务器启动 (listener 和 axum::serve) ...
}

6. 访问与验证

服务成功运行后,文档即可通过以下地址访问(假设默认端口为 8080):

访问目标 URL 作用
Swagger UI 交互页面 http://localhost:8080/swagger-ui 提供可测试的交互式界面。
OpenAPI 规范文件 http://localhost:8080/api-docs/openapi.json 原始的 JSON 格式 API 规范文件。